﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Security;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a shopping cart.
    /// </summary>
    public class CreateShoppingCart : CreateAuditable<ShoppingCartEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        return customer.EntityID == CustomerID;
                    });
            }
        }

        /// <summary>
        /// Gets or sets the customer identifier.
        /// </summary>
        public int CustomerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public ShoppingCartFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Construct(IUnitOfWork uow, ShoppingCartEntity entity)
        {
            base.Construct(uow, entity);

            entity.Customer = uow.Execute(new GetCustomer
            {
                ThrowsException = true,
                EntityID = CustomerID
            }).Result;
            entity.Total = Decimal.Zero;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the shopping cart.
    /// </summary>
    public class UpdateShoppingCart : UpdateAuditable<ShoppingCartEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var cart = uow.Execute(new GetShoppingCart
                        {
                            EntityID = EntityID
                        }).Result;
                        if(cart == null)
                            return false;
                        return cart.Customer.Equals(customer);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public ShoppingCartFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Revise(IUnitOfWork uow, ShoppingCartEntity entity)
        {
            base.Revise(uow, entity);

            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the shopping cart total.
    /// </summary>
    public class UpdateShoppingCartTotal : OperateEntityByID<ShoppingCartEntity>
    {
        #region Utilities

        protected override void Work(IUnitOfWork uow, out ShoppingCartEntity result)
        {
            var cart = uow.Execute(new GetShoppingCart
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;

            uow.Flush();
            uow.SqlQuery("exec fwk_ShoppingCarts_UpdateTotal @CartID=:CartID, @UpdatedOn=:UpdatedOn")
                .SetInt32("CartID", cart.EntityID)
                .SetDateTime("UpdatedOn", DateTime.UtcNow)
                .ExecuteUpdate();
            uow.Flush();
            
            result = uow.Execute(new RefreshEntity<ShoppingCartEntity>
            {
                Entity = cart
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the shopping cart.
    /// </summary>
    public class DeleteShoppingCart : DeleteEntity<ShoppingCartEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var cart = uow.Execute(new GetShoppingCart
                        {
                            EntityID = EntityID
                        }).Result;
                        if(cart == null)
                            return false;
                        return cart.Customer.Equals(customer);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Destruct(IUnitOfWork uow, ShoppingCartEntity entity)
        {
            foreach(var i in entity.Items.ToArray())
            {
                uow.Execute(new DeleteShoppingCartItem
                {
                    EntityID = i.EntityID
                });
            }
            foreach(var i in entity.Options.ToArray())
            {
                uow.Execute(new UnmapSalesOptionFromShoppingCart
                {
                    EntityID = entity.EntityID,
                    ChildID = i.EntityID
                });
            }
            base.Destruct(uow, entity);
        }

        #endregion
    }

    /// <summary>
    /// Gets the shopping cart by identifier.
    /// </summary>
    public class GetShoppingCart : GetEntity<ShoppingCartEntity>,
        IApiOperation
    {
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over shopping cart collection.
    /// </summary>
    public class SearchShoppingCarts : SearchOperation<ShoppingCartEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchShoppingCarts()
        {
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                ShoppingCartSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the customer identifier.
        /// </summary>
        public int? CustomerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sales item identifier.
        /// </summary>
        public int? SalesItemID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public ShoppingCartFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public ShoppingCartSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<ShoppingCartEntity> Build(IUnitOfWork uow, IQueryable<ShoppingCartEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Customer)
                .ThenFetch(x => x.Account);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            if(SalesItemID.HasValue)
            {
                query = query.Where(x => x.Items.Any(i =>
                    i.SalesItem.EntityID == SalesItemID.Value));
            }
            if(CustomerID.HasValue)
                query = query.Where(x => x.Customer.EntityID == CustomerID);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case ShoppingCartSortBy.Total:
                            query = query.OrderBy(x => x.Total, SortDir, i != 0);
                            break;
                        case ShoppingCartSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    #endregion

    #region __Map__

    /// <summary>
    /// Maps the option to cart.
    /// </summary>
    public class MapSalesOptionToShoppingCart : MapOperation
        <ShoppingCartEntity, SalesOptionEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var cart = uow.Execute(new GetShoppingCart
                        {
                            EntityID = EntityID
                        }).Result;
                        if(cart == null)
                            return false;
                        return cart.Customer.Equals(customer);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(uow.Execute(new GetSalesOption
            {
                ThrowsException = true,
                EntityID = ChildID
            }).Result.Flags.HasFlag(SalesOptionFlags.Item))
            {
                return Err.Of(this).NotAllowed();
            }
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out ShoppingCartEntity result)
        {
            base.Work(uow, out result);
            uow.Execute(new UpdateShoppingCartTotal
            {
                EntityID = EntityID
            });
        }

        #endregion
    }

    /// <summary>
    /// Unmaps the option from cart.
    /// </summary>
    public class UnmapSalesOptionFromShoppingCart : UnmapOperation
        <ShoppingCartEntity, SalesOptionEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var cart = uow.Execute(new GetShoppingCart
                        {
                            EntityID = EntityID
                        }).Result;
                        if(cart == null)
                            return false;
                        return cart.Customer.Equals(customer);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out ShoppingCartEntity result)
        {
            base.Work(uow, out result);
            uow.Execute(new UpdateShoppingCartTotal
            {
                EntityID = EntityID
            });
        }

        #endregion
    }

    #endregion

    #region __Workflow__

    /// <summary>
    /// Puts the required quantity of a sales item to a shopping cart.
    /// </summary>
    public class PutItemToShoppingCart : Operation<ShoppingCartItemEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var cart = uow.Execute(new GetShoppingCart
                        {
                            EntityID = CartID
                        }).Result;
                        if(cart == null)
                            return false;
                        return cart.Customer.Equals(customer);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the cart identifier.
        /// </summary>
        public int CartID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sales item identifier.
        /// </summary>
        public int SalesItemID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the quantity.
        /// </summary>
        public decimal Quantity
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price.
        /// </summary>
        public decimal Price
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the option identifiers.
        /// </summary>
        public int[] OptionIDs
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out ShoppingCartItemEntity result)
        {
            var cart = uow.Execute(new GetShoppingCart
            {
                ThrowsException = true,
                EntityID = CartID
            }).Result;
            var opts = OptionIDs != null
                ? OptionIDs.OrderBy(x => x)
                : Enumerable.Empty<Int32>();
            decimal absQuantity = Math.Abs(Quantity);
            var item = cart.Items.FirstOrDefault(x =>
                x.SalesItem.EntityID == SalesItemID &&
                x.Price == Price &&
                x.Options.Select(i => i.EntityID).OrderBy(i => i).SequenceEqual(opts));

            if(item == null)
            {
                item = uow.Execute(new CreateShoppingCartItem
                {
                    CartID = CartID,
                    SalesItemID = SalesItemID,
                    Quantity = absQuantity,
                    Price = Price
                }).Result;
                if(opts.Any())
                {
                    foreach(var i in opts)
                    {
                        uow.Execute(new MapSalesOptionToShoppingCartItem
                        {
                            EntityID = item.EntityID,
                            ChildID = i
                        });
                    }
                }
            }
            else
            {
                item = uow.Execute(new UpdateShoppingCartItem
                {
                    EntityID = item.EntityID,
                    Quantity = item.Quantity + absQuantity,
                    Price = item.Price
                }).Result;
            }

            result = item;
        }

        #endregion
    }

    /// <summary>
    /// Removes the required quantity of an item from a shopping cart.
    /// </summary>
    public class RemoveItemFromShoppingCart : Operation,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var item = uow.Execute(new GetShoppingCartItem
                        {
                            EntityID = CartItemID
                        }).Result;
                        if(item == null)
                            return false;
                        return item.Cart.Customer.Equals(customer);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the cart item identifier.
        /// </summary>
        public int CartItemID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the quantity.
        /// </summary>
        public decimal Quantity
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow)
        {
            var item = uow.Execute(new GetShoppingCartItem
            {
                EntityID = CartItemID
            }).Result;
            if(item == null)
                return;

            decimal absQuantity = Math.Abs(Quantity);
            decimal newQuantity = item.Quantity - absQuantity;

            if(newQuantity <= Decimal.Zero)
            {
                uow.Execute(new DeleteShoppingCartItem
                {
                    EntityID = item.EntityID
                });
            }
            else
            {
                item = uow.Execute(new UpdateShoppingCartItem
                {
                    EntityID = item.EntityID,
                    Quantity = newQuantity,
                    Price = item.Price
                }).Result;
            }
        }

        #endregion
    }

    /// <summary>
    /// Clears the shopping cart.
    /// </summary>
    public class ClearShoppingCart : OperateEntityByID,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var cart = uow.Execute(new GetShoppingCart
                        {
                            EntityID = EntityID
                        }).Result;
                        if(cart == null)
                            return false;
                        return cart.Customer.Equals(customer);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int? SellerID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow)
        {
            var cart = uow.Execute(new GetShoppingCart
            {
                EntityID = EntityID
            }).Result;
            if(cart == null)
                return;

            var items = SellerID.HasValue
                ? cart.Items.Where(x => x.SalesItem.Seller.EntityID == SellerID.Value)
                : cart.Items;
            if(!items.Any())
                return;

            foreach(var i in items.ToArray())
            {
                uow.Execute(new DeleteShoppingCartItem
                {
                    EntityID = i.EntityID
                });
            }
            if(!cart.Items.Any() && cart.Options.Any())
            {
                foreach(var o in cart.Options.ToArray())
                {
                    uow.Execute(new UnmapSalesOptionFromShoppingCart
                    {
                        EntityID = cart.EntityID,
                        ChildID = o.EntityID
                    });
                }
            }
        }

        #endregion
    }

    #endregion
}
